using System;
using System.Collections.Generic;
using System.IO;
using Cysharp.Threading.Tasks;
using Framework.Helper;
using Framework.Interface;
using Framework.Singleton;
using UnityEngine;
using UnityEngine.SceneManagement;
using UObject = UnityEngine.Object;

namespace Framework.Manager
{
    public class ResourceManager : MonoSingleton<ResourceManager>, IMonoManager
    {
        private readonly Dictionary<string, BundleRef> bundles = new();
        private readonly Dictionary<string, string> file2AssetBundleMap = new();
        private readonly Dictionary<string, bool> lockResource = new(StringComparer.OrdinalIgnoreCase);

        private string assetBundleLoadUrl;
        private AssetBundleManifest assetBundleManifest;

        private PackagerRules packagerRules;

        public async UniTask Initialize(Transform parent)
        {
            transform.SetParent(parent);
            
            await LoadPackagerRules();
            await LoadBundleManifest();
        }

        public void Shutdown()
        {
            Destroy(gameObject);
        }

        private async UniTask LoadBundleManifest()
        {
            assetBundleLoadUrl = Application.streamingAssetsPath + "/";
#if !UNITY_2021_3_OR_NEWER
            // UNITY_2021_OR_NEW不需要该判断
            if (Application.platform == RuntimePlatform.Android)
            {
                assetBundleLoadUrl = Application.dataPath + "!assets/";
            }
#endif
            
#if UNITY_ANDROID
            string manifestPath = assetBundleLoadUrl + "Android";
#elif UNITY_IPHONE
            string manifestPath = assetBundleLoadUrl + "iOS";
#elif UNITY_STANDALONE_WIN
            string manifestPath = assetBundleLoadUrl + "StandaloneWindows";
#elif UNITY_STANDALONE_OSX
            string manifestPath = assetBundleLoadUrl + "StandaloneMac";
#endif
            string hotfixPath = FileHelper.hotfixAssetbundleManifestPath;
            if (File.Exists(hotfixPath))
                manifestPath = hotfixPath;
            
            AssetBundle ab = await AssetBundleHelper.LoadAssetBundleAsync(manifestPath);
            if (ab == null)
            {
                Debug.LogError("[AssetBundleManager] manifest not found!");
                return;
            }
            
            assetBundleManifest = ab.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            ab.Unload(false);
        }

        private async UniTask LoadPackagerRules()
        {
            GameObject ruleGo = await Resources.LoadAsync<GameObject>("Prefabs/PackagerRules") as GameObject;
            if (ruleGo != null) packagerRules = ruleGo.GetComponent<PackagerRules>();
        }
        
        private string[] GetAllDependence(string assetPath)
        {
            List<string> list = new List<string>();

            string bundleName = LookupAssetBundleName(assetPath);
            string[] dependences = assetBundleManifest.GetAllDependencies(bundleName);

            list.Add(bundleName);
            list.AddRange(dependences);

            return list.ToArray();
        }
        
        public void AddLockResource(string name)
        {
            string[] assetNames = GetAllDependence(name);
            for (int i = 0; i < assetNames.Length; i++)
            {
                lockResource[GetPatchedAssetBundlePath(assetNames[i])] = true;
            }
        }

        public void RemoveLockResource(string name)
        {
            string[] assetNames = GetAllDependence(name);
            for (int i = 0; i < assetNames.Length; i++)
            {
                lockResource[GetPatchedAssetBundlePath(assetNames[i])] = false;
            }
        }
        
        public void Unload(bool unloadAllLoadedObjects)
        {
            foreach (KeyValuePair<string, BundleRef> elm in bundles)
            {
                if (elm.Value.refNum <= 0)
                {
                    if (!lockResource.ContainsKey(elm.Value.assetPath) || (lockResource.ContainsKey(elm.Value.assetPath) && !lockResource[elm.Value.assetPath]))
                    {
                        elm.Value.Unload(unloadAllLoadedObjects);
                    }
                }
            }
        }
        
        public void PrepareUnload()
        {
            foreach (KeyValuePair<string, BundleRef> elm in bundles)
            {
                elm.Value.refNum = 0;
            }
        }

        public async UniTask<GameObject> LoadPrefabAsync(string assetPath)
        {
            return await LoadAssetAsync<GameObject>(assetPath);
        }

        public async UniTask<Sprite> LoadSpriteAsync(string assetPath)
        {
            return await LoadAssetAsync<Sprite>(assetPath);
        }
        
        // 加载 T类型的资源
        public async UniTask<T> LoadAssetAsync<T>(string assetPath) where T : UObject
        {
            string bundleName = LookupAssetBundleName(assetPath);
            // Debug.Log("AA  " + assetPath + "  " + bundleName);
            AssetBundle assetBundle = await LoadBundleAsync(bundleName);
            return await GetAssetAsync<T>(assetBundle, assetPath);
        }
        
        public async UniTask LoadSceneAsync(string path, Action<AsyncOperation> onStart,
            Action<float> onLoading, Action<AsyncOperation> onFinish,
            LoadSceneMode mode = LoadSceneMode.Single)
        {
            string extension = Path.GetExtension(path);
            if (string.IsNullOrEmpty(extension) || !extension.Equals(".unity"))
            {
                Debug.Log($"should give valid scene path.");
                return;
            }

            string bundleName = LookupAssetBundleName(path);
            await LoadBundleAsync(bundleName);
            
            string sceneName = Path.GetFileNameWithoutExtension(path);
            await DoLoadSceneAsync(sceneName, mode, onStart, onLoading, onFinish);
        }
        
        // 加载文件夹下 T类型的所有资源
        public async UniTask<T[]> LoadAssetsAsync<T>(string assetPath) where T : UObject
        {
            string bundleName = $"{assetPath}{packagerRules.bundleExtension}";
            AssetBundle assetBundle = await LoadBundleAsync(bundleName);
            return await GetAssetsAsync<T>(assetBundle);
        }

        public async UniTask<AssetBundle> LoadBundleAsync(string bundleName)
        {
            BundleRef br;
            
            if (!bundles.TryGetValue(bundleName, out br))
            {
                br = new BundleRef(bundleName);
                bundles[bundleName] = br;
            }
            
            // 未加载过
            if (br.state == LoadState.None)
            {
                return await LoadSingleBundleAsync(bundleName);
            }
            else if (br.state == LoadState.Loading)
            {
                return await TillLoadedAssetBundleEnd(bundleName);
            }
            else
            {
                // Debug.Log("BB  " + bundleName + " " + br.state);
                return br.bundle;
            }
        }

        private async UniTask<AssetBundle> LoadSingleBundleAsync(string bundleName)
        {   
            BundleRef br;
            
            if (!bundles.TryGetValue(bundleName, out br) && br.bundle != null)
            {
                return br.bundle;
            }

            bundleName = bundleName.ToLower();

            string[] dependencies = assetBundleManifest.GetAllDependencies(bundleName);
            List<BundleRef> asyncList = new List<BundleRef>();

            for (int i = 0; i < dependencies.Length; i++)
            {
                br = null;

                if (string.IsNullOrEmpty(dependencies[i]) || string.IsNullOrEmpty(dependencies[i].Trim()))
                {
                    continue;
                }
                
                // 已经加载过了，增加计数
                if (bundles.TryGetValue(dependencies[i], out br) && br.bundle != null)
                {
                    br.refNum += 1;
                    continue;
                }

                if (br == null)
                {
                    br = new BundleRef(dependencies[i]);
                    bundles[dependencies[i]] = br;
                }

                if (br.request == null)
                {
                    br.state = LoadState.Loading;
                    
                    string dependPath = GetPatchedAssetBundlePath(dependencies[i]);
                    br.request = AssetBundleHelper.LoadBundleAsync(dependPath);
                }
                
                asyncList.Add(br);
            }

            br = null;
            if (bundles.TryGetValue(bundleName, out br) && br.request == null)
            {
                br.state = LoadState.Loading;
                
                string abPath = GetPatchedAssetBundlePath(bundleName);
                br.request = AssetBundleHelper.LoadBundleAsync(abPath);
            }
            asyncList.Add(br);
            
            for (int i = 0; i < asyncList.Count; i++)
            {
                BundleRef brDep = asyncList[i];
                if (brDep.bundle != null)
                {
                    continue;
                }

                if (brDep.request == null)
                {
                    continue;
                }

                if (brDep.request.isDone)
                {
                    brDep.bundle = brDep.request.assetBundle;
                    brDep.refNum += 1;
                    continue;
                }

                brDep.waitNum += 1;
                if (brDep.waitNum > 1)
                {
                    while (brDep.waitNum > 1 && !brDep.request.isDone)
                    {
                        await UniTask.Yield();
                    }
                }
                else
                {
                    await brDep.request;
                }
                brDep.waitNum -= 1;

                AssetBundle loadedAb = (brDep.bundle == null) ? brDep.request.assetBundle : brDep.bundle;
                brDep.bundle = loadedAb;
                brDep.state = LoadState.Loaded;
                brDep.refNum += 1;
                
                // 请求结束，去引用
                if (brDep.waitNum == 0)
                {
                    brDep.request = null;
                }
            }
            
            asyncList.Clear();
            
            return br.bundle;
        }

        private async UniTask<AssetBundle> TillLoadedAssetBundleEnd(string bundleName)
        {
            BundleRef br = bundles[bundleName];
            while (br.state != LoadState.Loaded)
            {
                await UniTask.DelayFrame(1);
            }

            return br.bundle;
        }

        private async UniTask<T> GetAssetAsync<T>(AssetBundle assetBundle, string assetPath) where T : UObject
        {
            if (assetBundle == null)
            {
                Debug.LogWarning("[ResourceManager] loadAssetCoroutine asset " + assetPath + " load failed, confirm pls");
                return null;
            }

            AssetBundleRequest request = assetBundle.LoadAssetAsync<T>(assetPath);
            await request;

            return request.asset as T;
        }

        private async UniTask<T[]> GetAssetsAsync<T>(AssetBundle assetBundle) where T : UObject
        {
            if (assetBundle == null)
            {
                Debug.LogWarning("[ResourceManager] loadAssetCoroutine  load failed, confirm pls");
                return null;
            }
            
            AssetBundleRequest request = assetBundle.LoadAllAssetsAsync<T>();
            await request;

            return Array.ConvertAll(request.allAssets, (asset) => (T)asset);
        }
        
        // 异步加载场景
        private async UniTask DoLoadSceneAsync(string scene, LoadSceneMode mode,
            Action<AsyncOperation> onStart, Action<float> onLoading, Action<AsyncOperation> onFinish)
        {
            AsyncOperation ao = SceneManager.LoadSceneAsync(scene, mode);
            if (onStart != null)
                onStart(ao);
            
            Debug.Log($"LoadSceneAsync ao isDone {ao.isDone}, progress {ao.progress}");
            while (!ao.isDone && ao.progress < 0.85f)
            {
                if (onLoading != null)
                    onLoading(ao.progress);

                await ao;
            }

            if (onFinish != null)
                onFinish(ao);
            
            Debug.Log($"load scene {name} end.");
        }
        
        // 根据资源名获取AssetBundle的名字
        private string LookupAssetBundleName(string assetPath)
        {
            if (!file2AssetBundleMap.TryGetValue(assetPath, out string bundleName))
            {
                bundleName = packagerRules.File2AssetBundleName(assetPath);
                file2AssetBundleMap[assetPath] = bundleName;
            }

            return bundleName;
        }
        
        // 获取最新AssetBundle路径，如果pachted过，则优先返回patched的路径，否则返回默认路径
        private string GetPatchedAssetBundlePath(string bundleName)
        {
            string patchedPath = HotfixManager.Instance.CheckHotfixAssetBundlePath(bundleName);
            if (string.IsNullOrEmpty(patchedPath))
            {
                patchedPath = assetBundleLoadUrl + bundleName;
            }

            return patchedPath;
        }
    }
}
